/*
 * Copyright 2020 Mike Penz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mikepenz.iconics.animation;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Rect;

import com.mikepenz.iconics.Iconics;
import com.mikepenz.iconics.IconicsBrush;
import com.mikepenz.iconics.utils.KtExtensions;

import java.util.ArrayList;
import java.util.List;

/**
 * @author pa.gulko zTrap (28.11.2018)
 */
public abstract class IconicsAnimationProcessor {
    private static final int DEFAULT_INTERPOLATOR = Animator.CurveType.LINEAR;

    /**
     * This value used used with the [repeatCount] property to repeat the animation
     * indefinitely.
     */
    public static final int INFINITE = AnimatorValue.INFINITE;

    /**
     * The time interpolator used in calculating the elapsed fraction of this animation. The
     * interpolator determines whether the animation runs with linear or non-linear motion, such
     * as acceleration and deceleration.
     */
    private int interpolator = DEFAULT_INTERPOLATOR;

    /**
     * The length of the animation. The default duration is 300 milliseconds. This value
     * cannot be negative.
     */
    private long duration = 300L;

    /**
     * Sets how many times the animation should be repeated. If the repeat
     * count is `0`, the animation is never repeated. If the repeat count is
     * greater than `0` or [INFINITE], the repeat mode will be taken
     * into account. The repeat count is [INFINITE] by default.
     */
    private int repeatCount = AnimatorValue.INFINITE;

    /**
     * Defines what this animation should do when it reaches the end. This
     * setting is applied only when the repeat count is either greater than
     * `0` or [INFINITE]. Defaults to [RepeatMode.RESTART].
     *
     * @see com.mikepenz.iconics.animation.ValueAnimator.RepeatMode
     */
    private ValueAnimator.RepeatMode repeatMode = ValueAnimator.RepeatMode.RESTART;

    private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);

    private IconicsAnimatedDrawable drawable;
    /**
     * Tag which will be used to apply this processor via xml
     */
    private String animationTag;

    /**
     * The set of listeners to be sent events through the life of an animation.
     */
    private List<IconicsAnimationListener> listeners = null;

    /**
     * The set of listeners to be sent pause/resume events through the life of an animation.
     */
    private List<IconicsAnimationPauseListener> pauseListeners = null;

    private final ValueAnimator.AnimatorListener proxyListener = new ValueAnimator.AnimatorListener() {
        @Override
        public void onAnimationStart(ValueAnimator animation) {
            KtExtensions.forEach(listeners, it ->
                it.onAnimationStart(IconicsAnimationProcessor.this));
        }

        @Override
        public void onAnimationEnd(ValueAnimator animation) {
            KtExtensions.forEach(listeners, it ->
                it.onAnimationEnd(IconicsAnimationProcessor.this));
        }

        @Override
        public void onAnimationStop(ValueAnimator animation) {
        }

        @Override
        public void onAnimationCancel(ValueAnimator animation) {
            KtExtensions.forEach(listeners, it ->
                it.onAnimationCancel(IconicsAnimationProcessor.this));
        }

        @Override
        public void onAnimationRepeat(ValueAnimator animation) {
            KtExtensions.forEach(listeners, it ->
                it.onAnimationRepeat(IconicsAnimationProcessor.this));
        }

        @Override
        public void onAnimationPause(ValueAnimator animation) {
            KtExtensions.forEach(pauseListeners, it ->
                it.onAnimationPause(IconicsAnimationProcessor.this));
        }

        @Override
        public void onAnimationResume(ValueAnimator animation) {
            KtExtensions.forEach(pauseListeners, it ->
                it.onAnimationResume(IconicsAnimationProcessor.this));
        }
    };

    /**
     * 构造方法
     */
    public IconicsAnimationProcessor() {
        create();
    }

    /**
     * 获取动画时长
     *
     * @return 动画时长
     */
    public long getDuration() {
        return duration;
    }

    /**
     * 设置动画时长
     *
     * @param duration 动画时长
     */
    public void setDuration(long duration) {
        this.duration = duration;
    }

    /**
     * 设置循环次数
     *
     * @param repeatCount 循环次数
     */
    public void setRepeatCount(int repeatCount) {
        this.repeatCount = repeatCount;
    }

    /**
     * 设置循环模式
     *
     * @param repeatMode 循环模式
     */
    public void setRepeatMode(ValueAnimator.RepeatMode repeatMode) {
        this.repeatMode = repeatMode;
    }

    /**
     * 设置动画标签
     *
     * @param animationTag 标签
     */
    public void setAnimationTag(String animationTag) {
        this.animationTag = animationTag;
    }

    /**
     * 获取动画标签
     *
     * @return 动画标签
     */
    public String getAnimationTag() {
        return animationTag;
    }

    /**
     * Whether the processor has been started and not yet ended.
     *
     * @return 是否动画开始
     */
    public boolean isStarted() {
        return animator.isRunning();
    }

    /**
     * Whether the processor has been started and not yet ended.
     *
     * @return 动画是否再执行
     */
    public boolean isRunning() {
        return animator.isRunning();
    }

    /**
     * Returns whether this processor is currently in a paused state.
     *
     * @return True if the processor is currently paused, false otherwise.
     */
    public boolean isPaused() {
        return animator.isPaused();
    }

    /**
     * Return the drawable's bounds Rect. Note: for efficiency, the returned object may be the same
     * object stored in the drawable (though this is not guaranteed).
     * <p>
     * Nullability contract: calling this into [processPreDraw] or [processPostDraw] is
     * guaranteed return `@NonNull` value, otherwise it can be `@Nullable`.
     *
     * @return The bounds of the drawable (which may change later, so caller beware). DO NOT ALTER
     * the returned object as it may change the stored bounds of this drawable.
     */
    public Rect getDrawableBounds() {
        return drawable.getCanvasBounds();
    }

    /**
     * Completed percent of animation
     *
     * @return 当前动画进度
     */
    public float getAnimatedPercent() {
        if (animator.getAnimatedValue() == null) {
            return 0;
        }
        return (float) animator.getAnimatedValue();
    }

    /**
     * Starts the animation, if processor is attached to drawable, otherwise sets flag to start
     * animation immediately after attaching
     *
     * @return 动画
     */
    public IconicsAnimationProcessor start() {
        animator.setInterpolatorType(interpolator);
        animator.setDuration(duration);
        animator.setRepeatCount(repeatCount);
        animator.setRepeatMode(repeatMode);

        boolean isStartRequested = false;
        if (drawable != null) {
            isStartRequested = false;
            animator.start();
        } else {
            isStartRequested = true;
        }
        return this;
    }

    /**
     * Adds a listener to the set of listeners that are sent events through the life of an
     * processor, such as start, repeat, and end.
     *
     * @param listener the listener to be added to the current set of listeners for this processor.
     * @return 动画
     */
    public IconicsAnimationProcessor addListener(IconicsAnimationListener listener) {
        if (listeners == null) {
            listeners = new ArrayList<>();
            animator.addListener(proxyListener);
        }
        listeners.add(listener);
        return this;
    }

    /**
     * Removes a listener from the set listening to this processor.
     *
     * @param listener the listener to be removed from the current set of listeners for this
     * processor.
     */
    public void removeListener(IconicsAnimationListener listener) {
        listeners.remove(listener);
        if (listeners.size() == 0) {
            listeners = null;
            animator.removeListener(proxyListener);
        }
    }

    /**
     * Adds a pause listener to this processor.
     *
     * @param listener the listener to be added to the current set of pause listeners for this processor.
     * @return 动画
     */
    public IconicsAnimationProcessor addPauseListener(IconicsAnimationPauseListener listener) {
        if (pauseListeners == null) {
            pauseListeners = new ArrayList<>();
            animator.addListener(proxyListener);
        }
        pauseListeners.add(listener);
        return this;
    }

    /**
     * Removes a pause listener from the set listening to this processor.
     *
     * @param listener the listener to be removed from the current set of pause listeners for
     * this processor.
     */
    public void removePauseListener(IconicsAnimationPauseListener listener) {
        pauseListeners.remove(listener);
        if (pauseListeners.size() == 0) {
            pauseListeners = null;
        }
    }

    /**
     * Removes all [listeners][addListener] and [pauseListeners][addPauseListener] from this
     * processor.
     */
    public void removeAllListeners() {
        if (listeners != null) {
            listeners.clear();
            listeners = null;
            animator.removeListener(proxyListener);
        }
        if (pauseListeners != null) {
            pauseListeners.clear();
            pauseListeners = null;
        }
    }

    /**
     * Cancels the animation. Unlike [end], [cancel] causes the animation to stop in its tracks,
     * sending an [IconicsAnimationListener.onAnimationCancel] to its listeners, followed by an
     * [IconicsAnimationListener.onAnimationEnd] message.
     * <p>
     * This method must be called on the thread that is running the processor.
     */
    public void cancel() {
        animator.cancel();
    }

    /**
     * Ends the animation. This causes the processor to assign the end value of the property being
     * animated, then calling the [IconicsAnimationListener.onAnimationEnd] method on its listeners.
     * <p>
     * This method must be called on the thread that is running the processor.
     */
    public void end() {
        animator.end();
    }

    /**
     * Plays the processor in reverse. If the processor is already running, it will stop itself
     * and play backwards from the point reached when reverse was called. If the processor is not
     * currently running, then it will start from the end and play backwards.
     */
    public void reverse() {
        animator.reverse();
    }

    /**
     * Pauses a running processor. This method should only be called on the same thread on which
     * the animation was started. If the animation has not yet been [started][isStarted] or has
     * since ended, then the call is ignored. Paused processors can be resumed by calling [resume].
     */
    public void pause() {
        animator.pause();
    }

    /**
     * Resumes a paused processor, causing the processor to pick up where it left off when it was
     * paused. This method should only be called on the same thread on which the processor was
     * started. Calls to [resume] on an processor that is not currently paused will be ignored.
     */
    public void resume() {
        animator.resume();
    }

    /**
     * Will be called before [draw(Canvas)][Drawable.draw].
     * Useful for some changes, based on [Paint]
     *
     * @param canvas 画布
     * @param iconBrush 画笔
     * @param iconContourBrush 边线画笔
     * @param backgroundBrush 背景画笔
     * @param backgroundContourBrush 背景边线画笔
     */
    public abstract void processorPreDraw(Canvas canvas,
        IconicsBrush<Paint> iconBrush,
        IconicsBrush<Paint> iconContourBrush,
        IconicsBrush<Paint> backgroundBrush,
        IconicsBrush<Paint> backgroundContourBrush
    );

    /**
     * Will be called after [draw(Canvas)][Drawable.draw].
     * Useful for some changes, based on canvas and need to restore canvas after drawing the icon
     * (scale, rotate etc.).
     *
     * @param canvas 画布
     */
    public abstract void processPostDraw(Canvas canvas);

    /**
     * Called when a drawable was attached and now [drawableBounds] and [drawableState] will
     * return valid values. Good place to set some drawable-dependent fields
     */
    protected void onDrawableAttached() {
    }

    /**
     * Called when a drawable was detached and now [drawableBounds] and [drawableState] will
     * return `null`. Good place to clear some drawable-dependent fields
     */
    protected void onDrawableDetached() {
    }

    /**
     * Internal set an drawable to this processor
     *
     * @param drawable 图片
     */
    protected void setDrawable(IconicsAnimatedDrawable drawable) {
        if (this.drawable != null) {
            this.drawable = null;
            onDrawableDetached();
        }
        this.drawable = drawable;
        if (drawable != null) {
            onDrawableAttached();
            start();
        } else {
            animator.cancel();
        }
    }

    /**
     * 创建动画
     */
    public void create() {
        Iconics.getInstance().registerProcessor(this);
    }
}
